对象


对象是JavaScript的基本数据类型:属性的无序集合。每个属性key: value和属性描述符descripter组成。

  • 属性名key:字符串或合法的变量标识符;

  • 属性值value:可以是任意JavaScript值(numberstringbooleannullundefined和数组、对象).
    value可以是gettersetter

  • 属性描述符descripter:每个属性的三个相关特性writableenumerableconfigurable,其值都是布尔类型,默认都为true


1 创建对象

创建对象有三种方法:对象字面量{a: 1}new Constructor()Object.create()

1.1 对象字面量

var book = {
    "main title": "JavaScript",                   //有空格或其他非标识符、关键字,必须使用双引号
    "sub-title": "The Definitive Guide",
    "for": "all audiences",
    author: {
        firstname: "David",
        lastname: "Flanagan"
    }
}

1.2 new操作符调用构造函数

可以使用new操作符调用一个构造器函数Constructor()创建一个对象。

var o = new String();
var d = new Date();

1.3 Object.create()方法

原型:每个JS对象都有一个与之相关联的原型对象prototype,对象其原型对象上继承属性。(当前对象中的__proto__属性是其原型对象的链接)

所有通过字面量创建的对象的原型都是Object.prototype,一个字面量对象{}相当于调用new Object()

Object.prototype的原型是null,所以null是原型的出口。

Object.create()是一个静态函数(不是提供给对象调用的方法),用于创建对象:

  • 第一个参数:原型对象,创建的新对象以该参数为原型;

  • 第二个参数:可选,用于对对象属性的描述

  • 创建一个没有原型的对象:Object.create(null);

  • 创建一个普通空对象({}new Object()):Object.create(Object.prototype);


2 对象三个特殊属性

每个对象都有与之相关的3个属性:原型prototype、类属性calss attribute、可扩展性extensible attribute

2.1 原型

每个JS对象都与另一个原型对象(prototype),利用原型可以实现继承。

  • 字面量的原型是:Object.prototype

  • new操作符创建的对象其原型:构造器函数的原型(prototype属性)

  • Object.create()创建的对象,其原型:第一个参数

查询对象的原型
  • ES5中定义了Object.getPrototypeOf()函数,来查询对象的原型

  • 利用new创建的对象会继承constructor属性,指向创建该对的构造器函数,所有该对象的原型是:obj.constructor.prototype

  • 字面量{}constructor属性指向Object()

  • 使用isPrototypeOf()方法查看一个对象是否为另一个对象的原型(或处于原型链中)

    Array.prototype.isPrototypeOf([]);   //  ==> true

2.2 类属性

对象的类属性class attribute是一个字符串,用来表示对象的信息。ES3与ES5均为提供设置类属性的API,只能通过继承自Object.prototypetoString()方法来简洁查询。

{}.toString();    //  ==> "[object Object]"

通过对返回字符串切片:第8位到倒数第2位即可获得对象的类型。但是许多对象重写了toString()方法,需要间接调用Functioncall()方法

function classof(o) {
    if(o === null) {return "Null";}
    if(o === undefined) {return "Undefined"}
    return Object.prototype.toString.call(o).slice(8, -1);
}

classof()方法可以返回传递给它的任意对象的类型,参数为numberstringboolean时,使用包装类型将其转化为对象,再进行操作。

    classof(null);  // ==> "Null"
    classof(2);  // ==> "Number"
    classof(false);  // ==> "Boolean"
    classof(new Date());  // ==> "Date"
    classof("");  // ==> "String"
    classof({});  // ==> "Object"
    classof([]);  // ==> "Array"
    classof(/./);  // ==> "RegExp"
    classof(window);  // ==> "Window",宿主对象
    function F() {};    //自定义一个构造器函数
    classof(new F());  // ==> "Object"

2.3 可扩展性extensible attribute

对象的课扩展性用来描述对象是否可以添加新的属性。所有的内置对象和自定义对象都是可扩展的,除非将其转化为不可扩展

  • Object.esExtensible()函数判断传入对象是否可以扩展;

  • Object.preventExensible()函数将传入的对象设置为不可扩展,并且过程不可逆;只影响对象本身的可扩展性

  • Object.seal()函数将对象设置为不可扩展,属性描述符configurable设置为false(不能添加新属性,已有的属性不能删除或配置,但是对于writable: true时,可以修改属性的值);过程不可逆

  • Object.isSealed()函数判断一个对象是否封闭

  • Object.freeze()函数将对象设置为不可扩展,属性描述符configurable: false;writable: false;只读。valueaccessor property含有setter函数,不受影响


3 组成对象的属性

每个对象是无需的属性集合,属性名可以是合法的变量标识符或字符串;变量值可以是任意JavaScript值;属性由描述符descripter来控制特性;

3.1 属性的查询与设置

属性可以通过.[]来访问:

  • 使用.访问时,其右侧必须是属性名称命名的简单标识符

  • 使用[]访问时,括号内必须是计算结果为字符串的表达式,字符串是属性的名字。变量名为关键字、含有空格或属性名是变量时,必须使用[]

    var author = book.author;
    var name = author.surname;
    var title = book["main title"];

属性的设置:与访问相似,将其放在赋值表达式的左侧

    book.edtion = 6;
    book["main title"] = "ECMAScript";
继承与错误

在查询一个对象是否存在时,先查看自身属性,如果没有;通过原型链逐层向上查找,直到原型链顶端null为止。

  • 如果自身属性与原型链中均为找到,属性访问返回undefined,不报错

  • 如果查询不存在对象的属性,会报错

  • nullundefined设置属性会报错

3.2 属性删除

使用delete操作符,可以删除对象的属性(其描述符中configurabletrue),并且只能删除自有属性,不能删除继承属性

3.3 属性检测

判断某个属性是否在某个对象中,JS有三种方法:inhasOwnProperty()propertyIsEnumerable()

  • in:如果对象自身属性或继承属性中包含该属性,返回true;

  • hasOwnProperty():只有对象自身属性包含该属性时,才返回true

  • propertyIsEnumerable():只有对象自身属性包含该属性,并且该属性是可以枚举(描述符中enumerable: true;

    var o = {a : 1};
    "a" in o;  //  ==> true
    o.hasOwnProperty("a");  //  ==> true
    o.propertyIsEnumerable("a");  //  ==> true

3.4 枚举属性

使用for-in循环可以遍历对象中所有可枚举的属性(包括自身属性与继承属性),把属性名赋值给循环变量。ES5定义了Object.keys()Object.getOwnPropertyNames()两个函数用来枚举属性名称。

  • Object.keys():返回对象中可枚举的自有属性名组成的数组

  • Object.getOwnPropertyNames():返回对象所有的自有属性名组成的数组,包括不可枚举的属性

对象继承的内置方法都是不可枚举的,给对象中增加的属性都可以枚举(除非设置其enumerable: false

有许多使用工具库为Object.prototype增加新的属性,这些属性可以被所有对象继承使用,但是在ES5之前,不能将其设置为不可枚举,所以在for-in循环时会被枚举出来。

//过滤继承的属性
for(p in o) {
    if(!o.hasOwnProperty(p)) {continue;}
}
//跳过方法
for(p in o) {
    if(typeof p === "function") {continue;}
}

操作对象属性的工具函数:

/**
 * 枚举属性的工具函数
 * 将对象p中可枚举的属性复制到对象o中,返回对象o;
 * 如果p和o含有同名的属性,则覆盖o中的属性
 * 不处理getter和setter以及复制属性
 */
function extend(o, p) {
  for(let prop in p) {
    o[prop] = p[prop];
  }
  return o;
}

/**
 * 将对象p中可枚举的属性复制到对象o中,返回对象o;
 * 如果o和p有同名属性,不影响o中的属性
 * 不处理getter和setter以及复制属性
 */
function merge(o, p) {
  for(let prop in p) {
    if(o.hasOwnProperty(prop)) {
      continue;
    }
    o[prop] = p[prop];
  }
  return o;
}

/**
 * 如果o中的属性在p中没有同名属性,从o中删除这个属性,并返回o
 */
function restrict(o, p) {
  for(let prop in o) {
    if(!(prop in p)) {
      delete o[prop];
    }
  }
  return o;
}

/**
 * 如果o中的属性在p中存在同名属性,从o中删除这个属性,并返回o
 */
function substrict(o, p) {
  for(let prop in o) {
    if(prop in p) {
      delete o[prop];
    }
  }
  return o;
}

/**
 * 返回一个数组,包含o中可枚举的自有属性的名字
 */
function keys(o) {
  //o必须是对象
  if(typeof o !== 'object') {
    throw TypeError();
  }
  var result = [];
  for(let prop in o) {
    if(o.hasOwnProperty(prop)) {
      result.push(prop);
    }
  }
  return result;
}

3.5 属性settergetter

属性的组成:keyvaluedescripter。在ES5中,value可以用一个或两个方法替代(gettersetter方法)

  • 所有JavaScript的value叫做数据属性data property

  • gettersetter定义的属性叫做存取器属性:accessor property

不同于data propertyaccessor property的读写属性由gettersetter决定。

  • 如果属性同时有gettersetter,它是一个可读写的属性;

  • 如果属性只有getter,它是一个只读的属性;

  • 如果属性只有setter,它是一个只写的属性;读取只写属性总是返回undefined

gettersetter定义
var o = {
    data_prop: value;        //普通数据属性
    
    //存取器属性是成对定义的函数,函数名是该属性名,没有冒号分隔函数体以属性名
    get accessor_prop() {...},
    set accessor_prop(value) {...}
}
var p = {
  //x、y是普通可读写的数据属性
  x: 1.0,
  y: 1.0,

  //r是可读写的存取器属性,具有getter和setter
  //函数结束后要带上逗号
  get r() {return Math.sqrt(this.x * this.x + this.y * this.y)},
  set r(newvalue) {
    var oldvalue = Math.sqrt(this.x * this.x + this.y * this.y);
    var ratio = newvalue / oldvalue;
    this.x *= ratio;
    this.y *= ratio;
  },
  //theta是只读存取器属性,只有getter方法
  get theta() {return Math.atan2(this.y, this.x)}
};
  • 函数体内的this指向表示这个点的对象

3.6 属性的描述符descripter

属性除keyvalue,还有一组用于描述其特性的descripter,其中有writableenumerableconfigurable三个属性,其值都为布尔类型,默认为true

假设将settergetter看做descripter,同理将属性的value也看做descripter

  • data property的四个特性:valuewritableenumerableconfigurable

  • accessory property的四个特性:gettersetterenumerableconfigurable

ES5提供操作descripter的API:

  • 调用Object.getOwnPropertyDescripter()函数,获取某个对象中特定自有属性的descirpter

  • 调用Object.getPrototypeOf()函数可以获取继承属性的descirpter

  • 调用Object.defineProperty()函数可以设置属性的特性,或者新建属性,使其具有某种特性

    //返回{ value: 1, writable: true, enumerable: true, configurable: true }
    Object.getOwnPropertyDescriptor({a: 1}, "a");
    
    //继承属性和不存在的属性,返回undefined
    Object.getOwnPropertyDescriptor({});
    Object.getOwnPropertyDescriptor({}, "toString");
    var o = {};   //创建空对象
    //添加一个不可枚举的属性,其值为1
    Object.defineProperty(o, "x", {value: 1, writable: true, enumerable: false, configurable: true});
    
    o.x;  //属性存在,但不可枚举, ==>1
    Object.keys(o);     //  ==>  []
    
    //对x属性进行修改,使其变为只读
    Object.defineProperty(o, 'x', {writable: false});
    
    o.x = 2;     //操作失败,但不报错;在严格模式下抛出类型错误
    o.x;    //  ==> 1
    
    //属性的值可以配置
    Object.defineProperty(o, 'x', {value: 2});
    o.x;    //  ==> 2
    
    //将x从数据属性修改为存取器属性
    Object.defineProperty(o, 'x', {get function() {return 0;}});
    o.x;   //==> 0

4 序列化对象

序列化对象指将对象的状态转化为字符串,同时可以将JSON字符串转化为对象。ES5内置JSON对象的JSON.stringgify()JSON.parse()可以完成序列化和解析。

var o = {x: 1, y: {z: [false, null, ""]}};   //测试对象
var s =JSON.stringify(o);         //  '{"x":1,"y":{"z":[false,null,""]}}'
var p = JSON.parse(s);         //p是s的深拷贝

JSON是JavaScript的子集,不能表示JavaScript中所有值。

  • 可以表示:objectArraystringnumberbooleannull,可以序列化与还原。infinityNaN序列化结果是nullDate对象序列化结果是日期字符串

  • 不能表示:函数、RegExpError对象和undefined

  • JSON.stringify()只能序列化对象自有的可枚举属性。


5 Object.prototype对象的方法

  • hasOwnProperty():检测该属性是否为对象自有属性

  • propertyIsEnumerable():检测该属性是否是对象自有,并且可枚举的属性

  • isPrototypeOf:检测某对象是否为另一个对象的原型

  • toString():无参数,返回调用该方法对象值的字符串,由于功能有限,某些对象重写了该方法:Array.toString()Date.toString()Function.toString()

  • toLoaclString()Object中的toLoaclString()只是调用toString(),只有DateNumber类对toLoaclString()定制,可以对数字、日期和时间进行本地化处理


6 总结

  1. JS中对象是属性的无序集合,每个对象有三个相关特性:原型prototype、类属性class property、可扩展性extensible

  2. 对象的创建三种方法:对象字面量、new调用构造函数和Object.create()

  3. 每个属性由三个部分组成:keyvaluedescripter

    • key只能是字符串或者值为字符串的变量标识符

    • value可以是任意JavaScript值(数据属性data property);可以是一个或两个方法,gettersetter(控制区属性accessor property

    • descripterwritableenumerableconfigurable,其值都是布尔类型,默认都为true。可以使用Object.defineProperty()操作描述符特性。

  4. writable指该属性是否可以写入数据;enumerable指该属性是否可以枚举,(使用for-in循环可以列出,ES5提供Object.keys()Object.getOwnPropertyNames());configurable指该属性是否可以用delete操作符删除和配置。


Kyxy
316 声望10 粉丝